feat: generic ecs material with texture type slots + shader uniforms#33
Conversation
Replace hardcoded render-system uniforms with material-driven approach: - ecs_component_material_t now stores shader.asset_id + shader.uniforms and texture.asset_ids[GL_TEXTURE_TYPE_COUNT] typed array - ecs_system_render_model reads material uniforms and resolves texture asset IDs to GL handles at render time - Model textures from glmodel_get_texuturelist also bound automatically - Auto-injected uniforms: view, projection, transform (per-frame), material.color, uBones (if skeleton present) - Updated mesh.h and serialization.h for new struct field layout
b42dd98 to
ed6baf6
Compare
|
|
||
| //load all animations | ||
| animator_load_all_animations(&o.animator, scene, o.arena, (animation_filter_t){0}); | ||
| animator_load_all_animations(&o.animator, scene, o.arena); |
rendercommand_t.material.texture is now a gltexturelist_t instead of flat u16 ids[], carrying per-texture type metadata through the entire render queue — from command submission to batch comparison to binding. Changes: - render_command.h: rendercommand material uses gltexturelist_t texture - render_command.h: compare_textures compares type + GL texture id - render_queue.h: dispatch binds via gltextureitem_t (2D or cubemap) - model.h: directly assign glmodel_get_texuturelist() into material, resolve per-type material texture asset IDs into typed items - mesh.h: construct gltexturelist_t for atlas texture
| struct { | ||
| u32 asset_ids[GL_TEXTURE_TYPE_COUNT]; | ||
| } texture; |
There was a problem hiding this comment.
change this to gltexturelist_t textures;
| gluniforms_t *uniforms = &cmd.material.shader.uniforms; | ||
|
|
||
| uniforms->data[uniforms->count].name = str("view"); | ||
| uniforms->data[uniforms->count].value.mat4 = glcamera_getview(ctx.active_camera); | ||
| uniforms->count++; | ||
|
|
||
| uniforms->data[uniforms->count].name = str("projection"); | ||
| uniforms->data[uniforms->count].value.mat4 = perspective_projection; | ||
| uniforms->count++; | ||
|
|
||
| uniforms->data[uniforms->count].name = str("transform"); | ||
| uniforms->data[uniforms->count].value.mat4 = model_transform; | ||
| uniforms->count++; | ||
|
|
||
| uniforms->data[uniforms->count].name = str("material.color"); | ||
| uniforms->data[uniforms->count].value.vec4 = *(vec4f_t *)list_get_value(&model->colors, idx); | ||
| uniforms->count++; | ||
|
|
||
| if (model->transforms[idx].len) { | ||
| uniforms->data[uniforms->count].name = str("uBones"); | ||
| uniforms->data[uniforms->count].value.mat4s.count = model->transforms[idx].len; | ||
| uniforms->data[uniforms->count].value.mat4s.data = (matrix4f_t *)model->transforms[idx].data; | ||
| uniforms->count++; | ||
| } | ||
|
|
||
| for (u8 u = 0; u < material->shader.uniforms.count; u++) { | ||
| uniforms->data[uniforms->count] = material->shader.uniforms.data[u]; | ||
| uniforms->count++; | ||
| } | ||
|
|
||
| uniforms->data[uniforms->count].name = str("light.color"); | ||
| uniforms->data[uniforms->count].value.vec4 = global_workbench->editor.current_selected_entity_id == entry->entity_id ? COLOR_RED : COLOR_WHITE; | ||
| uniforms->count++; | ||
|
|
||
| uniforms->data[uniforms->count].name = str("light.ambient"); | ||
| uniforms->data[uniforms->count].value.f32 = 1.0f; | ||
| uniforms->count++; | ||
|
|
||
| uniforms->data[uniforms->count].name = str("light.position"); | ||
| uniforms->data[uniforms->count].value.vec3 = vec3f(1.0f); | ||
| uniforms->count++; | ||
|
|
||
| cmd.material.texture = model_textures; | ||
|
|
||
| gltexturelist_t *texlist = &cmd.material.texture; | ||
| for (u8 t = 0; t < GL_TEXTURE_TYPE_COUNT; t++) { | ||
| if (!material->texture.asset_ids[t]) continue; | ||
| gltexture2d_t *tex = (gltexture2d_t *)assetmanager_get_assetresource( | ||
| &global_engine->systems.assets, ASSET_TYPE_TEXTURE, material->texture.asset_ids[t]); | ||
| if (tex && texlist->count < MAX_SUPPORTED_TEXTURE_COUNT_PER_DRAW_CALL) { | ||
| texlist->items[texlist->count++] = (gltextureitem_t){ | ||
| .type = (gltexturetype)t, | ||
| .source = { .normal_texture = tex } | ||
| }; | ||
| } | ||
| } |
There was a problem hiding this comment.
why is this done this way - we have the asset ids for the textures with us - also is there a better approach to the uniform assignment - maybe a regulator in bw the checks for these uniforms - and then update them as per how its ocnifured this would also makes it easier to bring about a consistent naming for the uniforms
Replaces hardcoded uniform injection blocks with a static registry table mapping uniform names to value sources: - ecs/uniform_registry.h: UNIFORM_REGISTRY[] table with 8 entries (view, projection, transform, material.color, uBones, light.*) plus uniform_registry_lookup(), uniform_registry_compute(), and uniform_registry_apply_material_overrides() - model.h / mesh.h: iterate shader->internal.uniformlocs, resolve each name via registry, eprint() crash on unknowns. Material.shader.uniforms overrides registry defaults.
|
|
||
| gluniforms_t *uniforms = &command.material.shader.uniforms; | ||
|
|
||
| hashtable_iterator(&shader->internal.uniformlocs, loc_entry) | ||
| { | ||
| const hashtable_entry_t *he = loc_entry; | ||
| const str_t uniform_name = he->key.str; | ||
| const uniform_binding_t *binding = uniform_registry_lookup(uniform_name); | ||
| if (!binding) | ||
| eprint("uniform '%.*s' not found in registry for shader '%.*s'", uniform_name.len, uniform_name.data, shader->vs.len, shader->vs.data); | ||
|
|
||
| uniforms->data[uniforms->count].name = uniform_name; | ||
| uniforms->data[uniforms->count].value = uniform_registry_compute(binding->source, &uniform_ctx); | ||
| uniforms->count++; | ||
| } | ||
|
|
||
| uniform_registry_apply_material_overrides(uniforms, &material->shader.uniforms); | ||
|
|
There was a problem hiding this comment.
move this prior to render command initalization - look if it can be assigned to the command member
| uniform_ctx.model_color = *(vec4f_t *)list_get_value(&model->colors, idx); | ||
| uniform_ctx.bones.count = model->transforms[idx].len; | ||
| uniform_ctx.bones.data = (matrix4f_t *)model->transforms[idx].data; | ||
|
|
There was a problem hiding this comment.
why are these set this way - not all models would have bones right ?
…r static bones - model.h / mesh.h: material.shader.uniforms overrides checked inline during registry iteration, removed standalone apply function - uniform_registry.h: UNIFORM_SOURCE_BONE_TRANSFORMS returns single identity matrix when bones.count == 0 (static models) - Removed now-unused uniform_registry_apply_material_overrides()
| .fields = { | ||
| CMP_FLD_DESC("\ttexture_asset_id:", "\ttexture_asset_id:%u", ecs_component_material_t, texture_asset_id, ECS_CMP_FLD_U32), | ||
| CMP_FLD_DESC("\tshader_asset_id:", "\tshader_asset_id:%u", ecs_component_material_t, shader_asset_id, ECS_CMP_FLD_U32), | ||
| CMP_FLD_DESC("\tshader_asset_id:", "\tshader_asset_id:%u", ecs_component_material_t, shader.asset_id, ECS_CMP_FLD_U32), |
| const u16 cmp_size = ecs_component__internal_get_componenttype_size(cmp_type); | ||
|
|
||
| buffer(WORD) buf = {0}; | ||
| buffer(KB * 3) buf = {0}; |
| typedef struct { | ||
| const glcamera_t *active_camera; | ||
| f32 aspect_ratio; | ||
| const ecs_component_transform_t *transform; | ||
| vec4f_t model_color; | ||
| struct { | ||
| u32 count; | ||
| matrix4f_t *data; | ||
| } bones; | ||
| bool is_selected; | ||
| } uniform_compute_ctx_t; |
There was a problem hiding this comment.
i dont think this should be here ? - this can be realsed during renderpass time
| { str_lit("light.position"), GL_UNIFORM_TYPE_VEC3F, UNIFORM_SOURCE_LIGHT_POSITION }, | ||
| }; | ||
| #define UNIFORM_REGISTRY_COUNT ARRAY_LEN(UNIFORM_REGISTRY) | ||
|
|
There was a problem hiding this comment.
dont use static use INTERNAL macro for the entire file
| static gluniform_value_t uniform_registry_compute(uniform_source_t source, const uniform_compute_ctx_t *ctx) | ||
| { | ||
| switch (source) | ||
| { | ||
| case UNIFORM_SOURCE_CAMERA_VIEW: | ||
| if (ctx->active_camera) return (gluniform_value_t){ .mat4 = glcamera_getview(ctx->active_camera) }; | ||
| break; | ||
|
|
||
| case UNIFORM_SOURCE_CAMERA_PROJECTION: | ||
| return (gluniform_value_t){ .mat4 = glms_perspective(radians(45), ctx->aspect_ratio, 1.0f, 1000.0f) }; | ||
|
|
||
| case UNIFORM_SOURCE_ENTITY_TRANSFORM: | ||
| if (ctx->transform) | ||
| return (gluniform_value_t){ .mat4 = glms_mat4_mul( | ||
| glms_translate_make(ctx->transform->position), | ||
| glms_mat4_mul( | ||
| glms_quat_mat4(ctx->transform->orientation), | ||
| glms_scale_make(ctx->transform->scale) | ||
| ) | ||
| )}; | ||
| break; | ||
|
|
||
| case UNIFORM_SOURCE_MODEL_COLOR: | ||
| return (gluniform_value_t){ .vec4 = ctx->model_color }; | ||
|
|
||
| case UNIFORM_SOURCE_BONE_TRANSFORMS: | ||
| if (ctx->bones.count > 0) | ||
| return (gluniform_value_t){ .mat4s = { .count = ctx->bones.count, .data = ctx->bones.data } }; | ||
| { | ||
| static matrix4f_t identity = GLMS_MAT4_IDENTITY_INIT; | ||
| return (gluniform_value_t){ .mat4s = { .count = 1, .data = &identity } }; | ||
| } | ||
|
|
||
| case UNIFORM_SOURCE_LIGHT_COLOR: | ||
| return (gluniform_value_t){ .vec4 = ctx->is_selected ? COLOR_RED : COLOR_WHITE }; | ||
|
|
||
| case UNIFORM_SOURCE_LIGHT_AMBIENT: | ||
| return (gluniform_value_t){ .f32 = 1.0f }; | ||
|
|
||
| case UNIFORM_SOURCE_LIGHT_POSITION: | ||
| return (gluniform_value_t){ .vec3 = (vec3f_t){1.0f, 1.0f, 1.0f} }; | ||
|
|
||
| case UNIFORM_SOURCE_COUNT: | ||
| break; | ||
| } | ||
| return (gluniform_value_t){0}; | ||
| } |
There was a problem hiding this comment.
most probably whats going to happen is that the ecs would have all this value required right ? - so instead remove this and have the depending on what uniformregistry need the shader requires, we provide those values to the renderpass shader memeber straight way assigning to the buffer.
| } break; | ||
| case ECS_CMP_MATERIAL: { | ||
| ecs_component_material_t *mat = &bundle->component[cmp_idx].material; | ||
| if (mat->texture_asset_id) mat->texture_asset_id = ecs_deserializer__internal_ensure_asset_loaded(assets, mat->texture_asset_id, assets_parsed); |
…AL macro - uniform_registry.h: removed uniform_compute_ctx_t and uniform_registry_compute(); registry is now pure lookup table. Functions use INTERNAL macro instead of static. - model.h / mesh.h: inline value computation via switch(binding->source) directly from local data (camera, transform, model, lights). Material overrides still applied inline. - component.h: comment justifying 3KB buffer for material component.
| hashtable_iterator(&shader->internal.uniformlocs, loc_entry) | ||
| { | ||
| const hashtable_entry_t *he = loc_entry; | ||
| const str_t uniform_name = he->key.str; | ||
| const uniform_binding_t *binding = uniform_registry_lookup(uniform_name); | ||
| if (!binding) | ||
| eprint("uniform '%.*s' not found in registry for shader '%.*s'", uniform_name.len, uniform_name.data, shader->vs.len, shader->vs.data); | ||
|
|
||
| gluniform_value_t value = {0}; | ||
| switch (binding->source) | ||
| { | ||
| case UNIFORM_SOURCE_CAMERA_VIEW: | ||
| value.mat4 = view_mat; | ||
| break; | ||
| case UNIFORM_SOURCE_CAMERA_PROJECTION: | ||
| value.mat4 = proj; | ||
| break; | ||
| case UNIFORM_SOURCE_ENTITY_TRANSFORM: | ||
| value.mat4 = entity_transform; | ||
| break; | ||
| case UNIFORM_SOURCE_MODEL_COLOR: | ||
| value.vec4 = mesh_color; | ||
| break; | ||
| case UNIFORM_SOURCE_BONE_TRANSFORMS: | ||
| if (bone_count > 0) { | ||
| value.mat4s.count = bone_count; | ||
| value.mat4s.data = bone_data; | ||
| } else { | ||
| static matrix4f_t identity = GLMS_MAT4_IDENTITY_INIT; | ||
| value.mat4s.count = 1; | ||
| value.mat4s.data = &identity; | ||
| } | ||
| break; | ||
| case UNIFORM_SOURCE_LIGHT_COLOR: | ||
| value.vec4 = is_selected ? COLOR_RED : COLOR_WHITE; | ||
| break; | ||
| case UNIFORM_SOURCE_LIGHT_AMBIENT: | ||
| value.f32 = 1.0f; | ||
| break; | ||
| case UNIFORM_SOURCE_LIGHT_POSITION: | ||
| value.vec3 = (vec3f_t){1.0f, 1.0f, 1.0f}; | ||
| break; | ||
| case UNIFORM_SOURCE_COUNT: | ||
| break; | ||
| } | ||
|
|
||
| for (u8 o = 0; o < material->shader.uniforms.count; o++) | ||
| if (str_cmp(material->shader.uniforms.data[o].name, uniform_name)) | ||
| value = material->shader.uniforms.data[o].value; | ||
|
|
||
| uniforms->data[uniforms->count].name = uniform_name; | ||
| uniforms->data[uniforms->count].value = value; | ||
| uniforms->count++; |
There was a problem hiding this comment.
do the inverse loop through all uniform names and then look it up in the shader - if not found crash using eprin
…nknowns Move material.shader.uniforms overrides to a dedicated pass after shader uniform population (instead of inline during iteration). Each material uniform is looked up in the populated list; unknown names trigger eprint() crash, catching accidental binding typos.
- Registry: LIGHT_COLOR/AMBIENT/POSITION sources replaced by single UNIFORM_SOURCE_MATERIAL — material.shader.uniforms is the only source for these values, crash on missing - model.h / mesh.h: switch handles MATERIAL case inline, reads value from material component. Removed 20-line separate override pass from both files. - model.h: dropped unused is_selected variable
| for (u8 o = 0; o < material->shader.uniforms.count; o++) { | ||
| if (str_cmp(material->shader.uniforms.data[o].name, uniform_name)) { | ||
| value = material->shader.uniforms.data[o].value; | ||
| found = true; | ||
| break; | ||
| } | ||
| } | ||
| if (!found) | ||
| eprint("material uniform '%.*s' not set for shader '%.*s'", uniform_name.len, uniform_name.data, shader->vs.len, shader->vs.data); | ||
| } |
There was a problem hiding this comment.
hard code the assignments - i dont see these changes if its in the uniform registry
| case ECS_CMP_MATERIAL: { | ||
| ecs_component_material_t *mat = &bundle->component[cmp_idx].material; | ||
| if (mat->texture_asset_id) mat->texture_asset_id = ecs_deserializer__internal_ensure_asset_loaded(assets, mat->texture_asset_id, assets_parsed); | ||
| if (mat->shader_asset_id) mat->shader_asset_id = ecs_deserializer__internal_ensure_asset_loaded(assets, mat->shader_asset_id, assets_parsed); |
There was a problem hiding this comment.
ensure tests are updated for ecs serialization
|
|
||
| gluniforms_t *uniforms = &command.material.shader.uniforms; | ||
|
|
||
| hashtable_iterator(&shader->internal.uniformlocs, loc_entry) |
There was a problem hiding this comment.
move this into a separate function that have it staright away assinged to rendercommand
Remove ecs/uniform_registry.h entirely. Each render system now writes its uniforms directly into the rendercommand — no lookup table, no source enum, no shared abstraction. model.h: view, projection, transform, material.color, uBones (conditional), material uniforms dumped wholesale. mesh.h: view, projection (instance shader baseline), material uniforms dumped wholesale. Removes ~100 lines of abstraction for no real loss at this scale.
New ecs_system_uniform_resolve runs before render systems and pre-populates material->shader.uniforms with per-frame values (view, projection, transform) from camera + entity data. Only fills uniforms the material hasn't already set. - ecs/uniform_registry.h: reinstated with resolve function - ecs/systems/uniform_resolve.h: new ECS system, iterates ECS_CMP_MATERIAL pool, resolves uniforms per entity - ecs/system.h: resolver registered after collider, before animation Render systems simplified: - model.h: copies resolved uniforms, adds only per-mesh material.color + uBones - mesh.h: copies resolved uniforms directly, zero uniform math
- test save files: material {totalmembers:1,bytesize:...} with
only shader_asset_id field (texture_asset_id removed)
- test source: updated ecs_component_material_t initialization
and assertions to use shader.asset_id
- build script: fixed include path for standalone worktree builds
Summary
Replaces the hardcoded render-system uniform layout with a material-driven approach, enabling different models to use different shaders with different uniform/texture layouts.
Built on top of #32 (embedded texture loading via materials).
Changes
1.
ecs/component/types.h— Extendedecs_component_material_t2.
ecs/systems/model.h— Material-driven render systemmaterial->shader.asset_idmaterial->texture.asset_ids[]to GL handlesglmodel_get_texuturelist()automatically3.
ecs/component.h— Capped material pool capacityAdded
ECS_CMP_MATERIAL: return 128case — prevents OOM since the new struct is ~1.4KB (was 8 bytes). Also increased the component copy buffer from WORD to 3*KB.4.
ecs/systems/mesh.h— Updated field accessmaterial->shader_asset_id→material->shader.asset_id5.
ecs/serialization.h— Updated material field mapSerializes
shader.asset_idas single field (texture arrays and uniforms are runtime-only, reconstructed from assets on load).Testing